FRECLIEN - Multithreaded client of a free-threaded server


NOTE: FRECLIEN uses CoInitializeEx. CoInitializeEx is implemented only on
Windows NT 4.0 and later versions. This sample will not load on Windows
95 or on earlier versions of Windows NT.


SUMMARY
=======

The FRECLIEN sample spawns multiple threads to create and use the COBall
COM object provided by the FRESERVE free-threaded server. COBall object
itself spawns no threads; it passively responds to IBall interface
requests from many possible client threads. The FRECLIEN client creates
and controls one COBall object through the IBall interface that the object
exposes. As three of FRECLIEN's threads move the ball through calls to
IBall::Move, the remaining main thread uses a system timer to obtain timed
updates of the COBall object's position, size, and color. This main thread
uses that data, obtained by calling the IBall::GetBall method, to display
graphical snapshot images of the ball in the client's main window.

As explained in the FRESERVE sample, the COBall object internally updates
its color property to reflect the last thread that called the object's
Move method. The display thread uses this data for each ball image it
displays. As the ball moves, it changes color to reflect each thread that
moves the ball. As the ball moves, it also leaves a trail that provides a
striking visual history of these passing threads. This trail demonstrates
that, with COM's free threading model, every thread that makes interface
requests to the same object all access the object on the calling thread.
Each different color of the single ball object represents a different
calling thread.

This free threading model is in contrast to the apartment model presented
in the APTSERVE and APTCLIEN samples. In apartment model threading, each
COM object is associated with an apartment thread. Calls from multiple
client threads are switched by COM to the thread of the apartment that
owns the object. Only one thread, the object's apartment thread, is
permitted to execute the methods of the object. This provides a
serialization of access to the object's data.

For functional descriptions and a tutorial code tour of FRECLIEN, see the
Code Tour section below. See also FRESERVE.TXT in the sibling FRESERVE
directory for more details on how FRESERVE works and exposes its services
to FRECLIEN. You must build the FRESERVE DLL before building FRECLIEN. The
makefile for FRESERVE automatically registers that server in the system
registry, so you must build FRESERVE before attempting to run FRECLIEN.
For details on the external user operation of FRECLIEN, see the Operation
section below.

For details on setting up your system to build and test the code samples
in this OLE Tutorial series, see TUTORIAL.TXT. The supplied MAKEFILE is
Microsoft NMAKE-compatible. To create a debug build, issue the NMAKE
command in the Command Prompt window.

Usage
-----

FRECLIEN is an application that you can execute directly from Windows in
the normal manner or from the Command Prompt window. No command line
parameters are recognized by FRECLIEN. FRECLIEN currently requires the
free-threading facilities provided in more recent releases of COM such as
found in the Beta 1 and later releases of version 4.0 of the Windows NT
Workstation operating system. The Windows 95 operating system does not
currently support free threading, so FRECLIEN will not run on it.


OPERATION
=========

The FRECLIEN.EXE application provides the user interface for this lesson.
It exercises the associated, but independent, FRESERVE.DLL to graphically
illustrate the free threading model. As the ball image created by FRECLIEN
moves, it leaves a trail. This trail shows a history of the various
threads that have executed the COBall object's Move method.

Here is a summary of operation from the standpoint of FRECLIEN.EXE as a
COM client of the FRESERVE.DLL COM server.

There is a minimal menu in FRECLIEN. All the operation is automatic. The
main application window's client area is used for visual display of the
moving ball.

Menu Selection: File/Exit
Exits FRECLIEN.

Menu Selection: Help/Read FRECLIEN.TXT
Opens the FRECLIEN.TXT file (this file) in the Windows Notepad.

Menu Selection: Help/Read FRESERVE.TXT
Opens the FRESERVE.TXT file from the sibling \FRESERVE directory in the
Windows Notepad.

Menu Selection: Help/Read Source File
Displays the Open common dialog box so you can open a source file from this
lesson or another one in the Windows Notepad.

Menu Selection: Help/About FRECLIEN
Displays the About dialog box for this application, a standard part of
this series of code samples. The code illustrates how to program the use
of the CAboutBox class provided by APPUTIL.LIB.


CODE TOUR
=========

Files          Description

FRECLIEN.TXT   This file.
MAKEFILE       The generic makefile for building the code sample
               application of this tutorial lesson.
FRECLIEN.H     The include file for the FRECLIEN application. Contains
               class declarations, function prototypes, and resource
               identifiers.
FRECLIEN.CPP   The main implementation file for FRECLIEN.EXE. Has WinMain
               and CMainWindow implementation, as well as the main menu
               dispatching.
FRECLIEN.RC    The application resource definition file.
FRECLIEN.ICO   The application icon resource.
GUIBALL.H      The class declaration for the CGuiBall C++ class.
GUIBALL.CPP    Implementation file for the CGuiBall C++ class.


FRECLIEN uses many of the utility classes and services provided by
APPUTIL. For more details on APPUTIL, study the APPUTIL library source
code and APPUTIL.TXT, which are located in the sibling \APPUTIL directory.

The program logic governing the ball's motion is encapsulated in the
COBall object. See the FRESERVE sample and FRESERVE.TXT for details.

The major topics covered in this code tour are initializing COM for free-
threaded access to the FRESERVE server and its components; the
construction of the CGuiBall C++ object; multithreading in FRECLIEN;
the use of the system timer; and shutdown of the client.

The main FRECLIEN process initializes COM for free-threaded operation.
It does this in the WinMain function. Here is the code from FRECLIEN.CPP.

  // Call to initialize the OLE COM Library.  Use the OLE SUCCEEDED macro
  // to detect success.  If fail then exit app with error message.
  // Tell COM that this client process and all subordinate threads
  // will live in a multi-threaded world. This means that all subsequent
  // COM objects created and functioned by this client must be coded
  // to be thread-safe. This is where we tell COM that we are using
  // the "Free-threading" model (rather than the default Apartment Model).
  if (SUCCEEDED(CoInitializeEx(NULL, COINIT_MULTITHREADED)))
  {
    ...
    ... Init application and enter main message loop, etc
    ...
  }
    ...

The CoInitializeEx function must be called with the COINIT_MULTITHREADED
flag. Only one thread in the process needs to call with this flag to
establish that all threads in the process as free-threaded. Any other
calls to CoInitializeEx by other threads in the process must also specify
COINIT_MULTITHREADED, or the function will return RPC_E_CHANGED_MODE. In
other words, within the process the threads must all consistently use the
same threading model and not attempt to change to another.

FRECLIEN uses a CGuiBall C++ class to encapsulate the data and behavior
of the client's graphical user interface (GUI) moving ball.

Here is the declaration of the CGuiBall class from file GUIBALL.H.

  class CGuiBall
  {
    private:
      HWND     m_hWnd;
      IBall*   m_pIBall;
      COLORREF m_crColor;

      // Pointers to thread init data structures.
      CThreadInitData m_BallThreadData1;
      CThreadInitData m_BallThreadData2;
      CThreadInitData m_BallThreadData3;

    public:
      // Some member variables to store thread ids.
      DWORD m_dwBallThread1;
      DWORD m_dwBallThread2;
      DWORD m_dwBallThread3;

      // An array of handles to the ball threads.
      HANDLE m_hBallThreads[3];

      CGuiBall(void);
      ~CGuiBall(void);
      BOOL Init(HWND hWnd);
      void PaintBall(void);
      void Restart(void);
      void PaintWin(void);
  };

The main process thread uses CGuiBall by calling its main public methods:
Init, Restart, PaintBall, and PaintWin. Calls through the IBall interface
on FRESERVE's COBall object are encapsulated within the methods of
CGuiBall. The COBall object is accessed through an IBall interface
pointer, m_pIBall, privately held in CGuiBall.

CGuiBall has an Init method in which an instance of FRESERVE's COBall
object is created. This Init method also spawns three threads whose sole
job is to continuously call COBall::Move to move the ball entity defined
in COBall. These three calling threads keep the ball moving while the main
display thread continuously paints snapshot images of the ball. CGuiBall
keeps the identifiers of these three threads. Several CThreadInitData
structures are used to create the threads.

Here is the CGuiBall::Init method definition from GUIBALL.CPP.

  BOOL CGuiBall::Init(
         HWND hWnd)
  {
    BOOL bOk = FALSE;
    HRESULT hr;

    if (hWnd)
    {
      m_hWnd = hWnd;

      // Call OLE service to create the single COBall instance.
      // We are not aggregating it so we ask for its IBall interface
      // directly.
      hr = CoCreateInstance(
             CLSID_DllBall,
             NULL,
             CLSCTX_INPROC_SERVER,
             IID_IBall,
             (PPVOID)&m_pIBall);
      if (SUCCEEDED(hr))
      {
        // Set up the client process to periodically paint the ball
        // thru WM_TIMER messages to the main Window proc.
        SetTimer(hWnd, 1, BALL_PAINT_DELAY, NULL);

        // Now start up 3 client BallThreads that will all try to move the
        // Ball concurrently. They will bring independent asynchronous life
        // to the ball. The main client process only displays the ball.

        // Create Structures for thread initialization.
        m_BallThreadData1.m_hWnd = hWnd;
        m_BallThreadData1.m_pIBall = m_pIBall;
        m_BallThreadData1.m_nDelay = BALL_MOVE_DELAY;
        m_BallThreadData2.m_hWnd = hWnd;
        m_BallThreadData2.m_pIBall = m_pIBall;
        m_BallThreadData2.m_nDelay = BALL_MOVE_DELAY;
        m_BallThreadData3.m_hWnd = hWnd;
        m_BallThreadData3.m_pIBall = m_pIBall;
        m_BallThreadData3.m_nDelay = BALL_MOVE_DELAY;

        // Create the Ball Moving Thread1.
        m_hBallThreads[0] = CreateThread(
                              0,
                              0,
                              (LPTHREAD_START_ROUTINE) BallThreadProc,
                              (LPVOID) &m_BallThreadData1,
                              0,
                              &m_dwBallThread1);

        bOk = (NULL != m_hBallThreads[0]);
        if (!bOk)
        {
          hr = GetLastError();
        }
        else
        {
          // AddRef for the copy handed out to Thread1.
          m_pIBall->Release();

          // Create the Ball Moving Thread2.
          m_hBallThreads[1] = CreateThread(
                                0,
                                0,
                                (LPTHREAD_START_ROUTINE) BallThreadProc,
                                (LPVOID) &m_BallThreadData2,
                                0,
                                &m_dwBallThread2);

          bOk = (NULL != m_hBallThreads[1]);
          if (!bOk)
          {
            hr = GetLastError();
          }
          else
          {
            // AddRef for the copy handed out to Thread2.
            m_pIBall->Release();

            // Create the Ball Moving Thread3.
            m_hBallThreads[2] = CreateThread(
                                  0,
                                  0,
                                  (LPTHREAD_START_ROUTINE) BallThreadProc,
                                  (LPVOID) &m_BallThreadData3,
                                  0,
                                  &m_dwBallThread3);

            bOk = (NULL != m_hBallThreads[2]);
            if (!bOk)
            {
              hr = GetLastError();
            }
            else
            {
              // AddRef for the copy handed out to Thread3.
              m_pIBall->Release();
            }
          }
        }
      }
    }

    return (bOk);
  }

CoCreateInstance is called to create an instance of the FRESERVE server's
COBall object specified by its CLSID, CLSID_DllBall. This CLSID is defined
in BALLGUID.H, located in the sibling INC directory. Because aggregation
is not required, CoCreateInstance directly requests the IBall interface,
which on return is stored in m_pIBall. The Init method then launches the
three moving threads after setting appropriate CThreadInitData structures.
This is the CThreadInitData as declared in GUIBALL.H.

  // A small utility struct providing an encapsulation of data needed when
  // worker threads are initialized.
  struct CThreadInitData
  {
    HWND   m_hWnd;
    IBall* m_pIBall;
    DWORD  m_nDelay;
  };

As presented in the previous APTSERVE sample, a pointer to a
CThreadInitData structure is passed as an LPARAM to the thread procedure
of the newly launched thread. Using this structure, the client's main
window handle, an interface pointer to the IBall interface on the COBall
object, and a value for the time delay between ball movements are all
passed to the new threads. Following the COM contract, the Init method
calls m_pIBall->AddRef for each copy of the interface pointer returned to
the newly created thread. These AddRef calls are later matched by Release
calls in the CGuiBall destructor.

The m_hBallThreads array holds handles to the created threads. It is used
for later calls to WaitForMultipleObjects to ensure an orderly shutdown of
the threads before the client program exits. This occurs in the CGuiBall
destructor.  Here is the destructor from GUIBALL.CPP.

  CGuiBall::~CGuiBall(void)
  {
    BOOL bOk = TRUE;

    if (m_pIBall)
    {
      // Kill the client's app timer for its repaints.
      KillTimer(m_hWnd, 1);

      // Call down to the server's COBall and tell it to shutdown.
      m_pIBall->Move(FALSE);

      // Wait for the threads to terminate before closing their
      // thread handles.
      WaitForMultipleObjects(3, m_hBallThreads, TRUE, INFINITE);
      for (size_t i = 0; i<3; i++)
        CloseHandle(m_hBallThreads[i]);

      // Release for each of the thread copies handed out.
      RELEASE_INTERFACE(m_pIBall);
      RELEASE_INTERFACE(m_pIBall);
      RELEASE_INTERFACE(m_pIBall);
      // Release for the main copy held in CGuiBall.
      RELEASE_INTERFACE(m_pIBall);
    }
  }

The main process thread relies on a recurrent system timer's WM_TIMER
message. This was set up within the Init method of CGuiBall shown above.
These periodic timer messages drive the client's independent asynchronous
display process. The timer is killed in the destructor (see above) with
the KillTimer call. Each WM_TIMER message sent to the main window
procedure is honored by a call to the COBall::GetBall method to obtain the
ball's display data. Here is the main window procedure from FRECLIEN.CPP.

  LRESULT CMainWindow::WindowProc(
            UINT uMsg,
            WPARAM wParam,
            LPARAM lParam)
  {
    LRESULT lResult = FALSE;

    switch (uMsg)
    {
      case WM_CREATE:
        break;

      case WM_MEASUREITEM:
        // Get setup for painting text in this window.
        {
          LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam;
          lpmis->itemHeight = m_tm.tmHeight + m_tm.tmExternalLeading;
          lpmis->itemWidth = m_wWidth;
          lResult = TRUE;
        }

      case WM_SIZE:
        // Handle a resize of this window.
        m_wWidth = LOWORD(lParam);
        m_wHeight = HIWORD(lParam);
        // Handle a resize of this window.
        // Restart the ball from upper left, clear window.
        m_pGuiBall->Restart();
        break;

      case WM_TIMER:
        // This is our timed attempt to continuously paint the moving ball.
        // It doesn't move it. Other non-GUI threads move the virtual ball.
        m_pGuiBall->PaintBall();
        break;

      case WM_COMMAND:
        // Dispatch and handle any Menu command messages received.
        lResult = DoMenu(wParam, lParam);
        break;

      case WM_CHAR:
        if (wParam == 0x1b)
        {
          // Exit this app if user hits ESC key.
          PostMessage(m_hWnd,WM_CLOSE,0,0);
          break;
        }
      case WM_LBUTTONUP:
      case WM_PAINT:
        // If something major happened or user clicks or hits key then
        // repaint the whole window.
        m_pGuiBall->PaintWin();
        break;

      case WM_CLOSE:
        // The user selected Close on the main window's System menu.
      case WM_QUIT:
        // If the app is quit by the File/Exit main menu then close
        //   any associated help windows too.
        ::WinHelp(m_hWnd, m_szHelpFile, HELP_QUIT, 0);
      default:
        // Defer all messages NOT handled here to the Default Window Proc.
        lResult = ::DefWindowProc(m_hWnd, uMsg, wParam, lParam);
        break;
    }

    return(lResult);
  }

The CGuiBall::PaintBall method is called to paint the ball when the
WM_TIMER messages arrive. Other main client application messages of
interest are WM_SIZE, WM_PAINT, and WM_LBUTTONUP. For WM_SIZE, if the user
resizes the client application's main window, CGuiBall::Restart is called.
The WM_SIZE message handler calls COBall::Reset, which relocates the ball
to the upper left corner of the window and also resets the ball size. If
the user clicks the left mouse button (causing WM_LBUTTONUP) or a general
WM_PAINT condition occurs, the window is repainted, but the ball is not
reset.

The threads each run the following common thread procedure, BallThreadProc
in GUIBALL.CPP.

  DWORD WINAPI BallThreadProc(
                 LPARAM lparam)
  {
    CThreadInitData* pInitData = (CThreadInitData*) lparam;
    HRESULT hr;
    DWORD nEndCount = 0;
    BOOL bAlive = TRUE;
    DWORD nDelay;

    // Keep a copy here on the local stack of the ball move delay.
    nDelay = pInitData->m_nDelay;

    // Initialize COM for use by this thread. Tell COM we are
    // multi-threaded.
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);

    // Continuously move the ball while it is still alive.
    while (bAlive)
    {
      // Use system timer to slow down the ball motion to the range
      // of the humanly perceptible.
      if (GetTickCount() > nEndCount)
      {
        // After the delay, call from this thread thru IBall interface to
        // move the single ball that lives in the COBall COM object.
        bAlive = pInitData->m_pIBall->Move(TRUE);

        // Set new timer end count.
        nEndCount = GetTickCount() + nDelay;
      }
    }

    // UnInitialize COM for use by this thread.
    CoUninitialize();

    return 0;
  }

Within each launched thread, COM is initialized specifying
COINIT_MULTITHREADED to indicate free threading. Each of the three threads
executes this same re-entrant thread procedure. Within the procedure, each
thread executes an indefinite while loop containing a call to the
IBall::Move method on the COBall object. Because these move calls would be
far too fast for human perception, each thread is kept busy in a delay
countdown using the system timer tick count. This sample uses delays in
the neighborhood of 100 milliseconds. When the delay is over, the Move
method is called on the COBall object. The three threads are running
concurrently and could make the Move calls at conflicting times. As
explained in the FRESERVE sample, the Move method on COBall is guarded by
the CThreaded OwnThis mechanism. If one of the threads currently owns the
COBall object, a Move call by another thread will block the calling thread
until it can obtain ownership.

Each thread's while loop terminates in BallThreadProc when the Move call
that is made there returns FALSE. This return signals that some other
thread had killed the ball by calling Move call with the bAlive parameter
set to FALSE. This terminating call is actually made by the main
application thread and not by any of the ball-moving threads. The call is
made in the CGuiBall destructor shown above. The destructor is run when
the main CGuiBall object is deleted because the user has closed or exited
the main application, causing an exit of the main process thread's message
loop in WinMain.
